/*
 * @FilePath     : /clock/components/drivers/ST7735S.c
 * @Author       : Ma Xiaowei
 * @Date         : 2020-08-11 09:18
 * @LastEditTime : 2020-08-12 16:15
 * @LastEditors  : Ma Xiaowei
 * @Description  : ST7735S的驱动程序，使用SPI2
 */
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "ST7735S.h"

/*******************************************************
 *                Type Definitions
 *******************************************************/
typedef struct {
    uint8_t cmd;        /* 指令 */
    uint8_t data[8];    /* 参数, 目前最大个数为6 */
    uint8_t count;      /* 参数个数 */
} st_cmd_t;


/*******************************************************
 *                Constants
 *******************************************************/
static const char *TAG = "ST7735S";
static const st_cmd_t init_cmds[] = {
    // 控制显示方向，9.11.4(P77)
    // {0x36, {(1<<5)|(1<<6)}, 1}, /* MV=MX=1,other=0,顺时针旋转90°,'RGB' */
    // 颜色编码16bit(RGB-565)
    {0x3a, {0x55}, 1},

    /* 帧率 */
    // normal mode/Full colors,Fr = fosc/((RTNA*2+40)*(LINE+FPA+BPA+2))
    {0xb1, {0x01,0x2c,0x2d}, 3},    /* 850k/(42*(L+44+45+2))=80 */
    // idle mode/8-colors
    {0xb2, {0x01,0x2c,0x2d}, 3},
    // partial mode/full
    {0xb2, {0x01,0x2c,0x2d,0x01,0x2c,0x2d}, 6},

    /* 电压设定 */
    {0xc0, {0xa2,0x02,0x84}, 3},
    {0xc1, {0xc5}, 1},
    {0xc2, {0x0a,0x00}, 2},
    {0xc3, {0x8a,0x2a}, 2},
    {0xc4, {0x8a,0xee}, 2},

    /* VCOM */
    {0xc5, {0x0e}, 1},
};
/*******************************************************
 *                Variable Definitions
 *******************************************************/
static spi_device_handle_t spi_st;

/*******************************************************
 *                Function Declarations
 *******************************************************/
static void spi_pre_callback(spi_transaction_t *t);
static void write_cmd(uint8_t cmd);
static void write_data(const uint8_t *data, uint8_t len);
static void spi_init(void);
static void device_init(void);



/*******************************************************
 *                Function Definitions
 *******************************************************/

/**
 * @brief spi发送数据前的回调函数，此处用于设定DC引脚的值
 * @param t 传输的数据，需要解析其中的user成员
 */
static void spi_pre_callback(spi_transaction_t *t)
{
    uint32_t dc = (uint32_t)t->user;
    gpio_set_level(PIN_NUM_DC, dc);
    // ESP_LOGI(TAG, "DC_PIN_SET:%d",dc);
}

/**
 * @brief 使用轮询模式通过SPI2向驱动IC写命令
 * @param cmd 需要写入的命令
 */
static void write_cmd(uint8_t cmd)
{
    esp_err_t ret;
    // 设定传输数据
    spi_transaction_t t = {0};
    t.length = 8;       /* 传输8bit */
    t.tx_buffer = &cmd;
    t.user = (void *)0; /* 用户自定义字段，用于设定DC的值 */

    // 传输数据
    ret = spi_device_polling_transmit(spi_st, &t);

    assert(ret == ESP_OK);
}

/**
 * @brief 使用轮询模式通过SPI2向驱动IC写数据
 * @param data 需要写入的数据的地址
 * @param len  数据长度
 */
static void write_data(const uint8_t *data, uint8_t len)
{
    esp_err_t ret;
    spi_transaction_t t = {0};

    if(len == 0) {
        return; /* 不需要发送数据 */
    }

    // 设定发送的参数
    t.length = len * 8;
    t.tx_buffer = data;
    t.user = (void *)1; /* 用户自定义字段，用于设定DC的值 */

    // 传输数据
    ret = spi_device_polling_transmit(spi_st, &t);

    assert(ret == ESP_OK);
}

/**
 * @brief 初始化SPI
 */
static void spi_init(void)
{
    // TODO: SPI可能由多个设备同时使用，应该将其初始化部分独立出来
    esp_err_t ret;

    spi_bus_config_t buscfg = {
        .mosi_io_num = PIN_NUM_MOSI,
        .sclk_io_num = PIN_NUM_CLK,
        .miso_io_num = -1,      /* 未使用 */
        .quadwp_io_num = -1,    /* 未使用 */
        .quadhd_io_num = -1,    /* 未使用 */
        .max_transfer_sz = 0,   /* 0即为默认值 4096bytes */
    };
    spi_device_interface_config_t devcfg={
        .clock_speed_hz = SPI_MASTER_FREQ_10M,  /* 传输速率 */
        .mode = 0,                              /* SPI模式 */
        .spics_io_num = PIN_NUM_CS,             /* CS引脚 */
        .queue_size = 5,                        /* 队列长度，用于中断方式 */
        .pre_cb = spi_pre_callback,             /* 发送前的回调函数 */
    };

    // 初始化spi
    ret = spi_bus_initialize(LCD_HOST, &buscfg, DMA_CHAN);
    ESP_ERROR_CHECK(ret);

    // 将LCD加入到spi总线
    ret = spi_bus_add_device(LCD_HOST, &devcfg, &spi_st);
    ESP_ERROR_CHECK(ret);
}

/**
 * @brief 初始化驱动的设置
 */
static void device_init(void)
{
    int i = 0;

    // 设定其余引脚
    gpio_set_direction(PIN_NUM_DC, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_NUM_BCKL, GPIO_MODE_OUTPUT);

    // 复位lcd
    gpio_set_level(PIN_NUM_RST, 0);
    vTaskDelay(100 / portTICK_RATE_MS);
    gpio_set_level(PIN_NUM_RST, 1);
    vTaskDelay(50 / portTICK_RATE_MS);

    // Sleep out
    write_cmd(0x11);
    vTaskDelay(120 / portTICK_RATE_MS);

    // 发送初始化命令
    for(i = 0; i < (sizeof(init_cmds)/sizeof(st_cmd_t)); i++)
    {
        write_cmd(init_cmds[i].cmd);
        write_data(init_cmds[i].data, init_cmds[i].count);
    }

    // 打开背光
    // TODO: 利用pwm调光(LED Control)
    gpio_set_level(PIN_NUM_BCKL, 1);
    vTaskDelay(50 / portTICK_RATE_MS);
}

/**
 * @brief 驱动初始化，包括初始化spi和芯片
 */
void st_init(void)
{
    // 初始化spi
    spi_init();

    // 初始化lcd
    device_init();
}

/**
 * @brief 设定写入数据的区域位置
 * @param xs 起始列
 * @param xe 结束列
 * @param ys 起始行
 * @param ye 结束行
 */
void st_set_region(uint16_t xs, uint16_t xe, uint16_t ys, uint16_t ye)
{
    uint8_t buf[4];
    // TODO:增加参数检查

    // 设定x方向区域
    write_cmd(0x2a);
    buf[0] = xs >> 8;   /* xs high */
    buf[1] = xs & 0xff; /* xs low */
    buf[2] = xe >> 8;   /* xe high */
    buf[3] = xe & 0xff; /* xe low */
    write_data(buf, 4);

// 设定y方向区域
    write_cmd(0x2b);
    buf[0] = ys >> 8;   /* ys high */
    buf[1] = ys & 0xff; /* ys low */
    buf[2] = ye >> 8;   /* ye high */
    buf[3] = ye & 0xff; /* ye low */
    write_data(buf, 4);

    write_cmd(0x2c);
}